﻿using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Text;

namespace AnnieSharp
{
    /// <summary>
    /// 示对象抽象类,不能直接实例化。一切显示对象的基类,包含了显示对象需要的一切属性
    /// DisplayObject 类本身不包含任何用于在屏幕上呈现内容的 API。
    /// 因此，如果要创建 DisplayObject 类的自定义子类，您将需要扩展其中一个具有在屏幕
    /// 上呈现内容的 API 的子类，如 Shape、Sprite、Bitmap、TextField 或 MovieClip 类。
    /// 与AnnieJs差异：移除了_setProperty
    /// </summary>
    public abstract class DisplayObject : EventDispatcher
    {
        public DisplayObject()
        {
            _instanceType = "annie.DisplayObject";
        }

        protected UpdateObjectConfig _UI = new UpdateObjectConfig()
        {
            UD = false,
            UM = true,
            UA = true,
            UF = false
        };

        /// <summary>
        /// 此显示对象所在的舞台对象,如果此对象没有被添加到显示对象列表中,此对象为空。
        /// </summary>
        public Stage Stage { get; set; } = null;

        /// <summary>
        /// 显示对象的父级
        /// </summary>
        public Sprite Parent { get; set; } = null;


        /// <summary>
        /// 显示对象在显示列表上的最终表现出来的透明度,此透明度会继承父级的透明度依次相乘得到最终的值
        /// </summary>
        protected float cAlpha = 1;

        /// <summary>
        /// 显示对象上对显示列表上的最终合成的矩阵,此矩阵会继承父级的显示属性依次相乘得到最终的值
        /// </summary>
        protected Matrix cMatrix = new Matrix();

        /// <summary>
        /// 获取显示对象在显示列表上的最终表现出来的透明度
        /// </summary>
        /// <returns></returns>
        public Matrix CMatrix
        {
            get
            {
                return cMatrix;
            }
        }

        /// <summary>
        /// 获取显示对象在显示列表上的最终表现出来的透明度
        /// </summary>
        /// <returns></returns>
        public float CAlpha
        {
            get
            {
                return cAlpha;
            }
        }


        /// <summary>
        /// 显示对象上对显示列表上的最终的所有滤镜组
        /// </summary>
        protected List<Filter> cFilters = new List<Filter>();

        /// <summary>
        /// 是否可以接受点击事件,如果设置为false,此显示对象将无法接收到点击事件
        /// </summary>
        public bool MouseEnable { get; set; } = true;

        /// <summary>
        /// 每一个显示对象都可以给他命一个名字,这样我们在查找子级的时候就可以直接用this.getChildrndByName("name")获取到这个对象的引用
        /// </summary>
        public string Name { get; set; } = "";


        #region 基础属性
        /// <summary>
        /// 显示对象位置x
        /// </summary>
        public float X { get; set; }
        /// <summary>
        /// 显示对象位置y
        /// </summary>
        public float Y { get; set; }
        /// <summary>
        /// 显示对象x方向的缩放值
        /// </summary>
        public float ScaleX { get; set; }
        /// <summary>
        /// 显示对象y方向的缩放值
        /// </summary>
        public float ScaleY { get; set; }
        /// <summary>
        /// 显示对象旋转角度
        /// </summary>
        public float Rotation { get; set; }
        /// <summary>
        /// 显示对象透明度
        /// </summary>
        public float Alpha { get; set; }
        /// <summary>
        /// 显示对象x方向的斜切值
        /// </summary>
        public float SkewX { get; set; }
        /// <summary>
        /// 显示对象上x方向的缩放或旋转点
        /// </summary>
        public float AnchorX { get; set; }
        /// <summary>
        /// 显示对象上y方向的缩放或旋转点
        /// </summary>
        public float AnchorY { get; set; }

        private bool _visible = true;

        public bool Visible
        {
            get
            {
                return _visible;
            }
            set
            {
                if (value != _visible)
                {
                    if (value)
                    {
                        ParentChanged = true;
                    }
                    _visible = value;
                }
            }
        }

        /// <summary>
        /// 显示对象的变形矩阵
        /// </summary>
        public Matrix Matrix { get; } = new Matrix();

        /// <summary>
        /// 是否自己的父级发生的改变
        /// 对应AnnieJs _cp
        /// </summary>
        public bool ParentChanged { get; set; } = true;

        private DisplayObject _mask = null;

        /// <summary>
        /// 显示对象的遮罩, 是一个Shape显示对象或是一个只包含shape显示对象的MovieClip
        /// </summary>
        public DisplayObject Mask
        {
            get
            {
                return _mask;
            }
            set
            {
                if (value != _mask)
                {
                    if (value != null)
                    {
                        value._isUseToMask++;
                    }
                    else
                    {
                        if (_mask != null)
                        {
                            _mask._isUseToMask--;
                        }
                    }
                    _mask = value;
                }
            }
        }

        /// <summary>
        /// 显示对象的滤镜数组
        /// </summary>
        public List<Filter> Filters { get; set; }

        #endregion


        /// <summary>
        /// 将全局坐标转换到本地坐标值
        /// </summary>
        /// <param name="point"></param>
        /// <param name="bp"></param>
        /// <returns></returns>
        public Point GlobalToLocal(Point point, Point bp = null)
        {
            //return this.cMatrix.invert().transformPoint(point.x, point.y, bp);
            throw new Exception("未实现DisplayObject.GlobalToLocal");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="point"></param>
        /// <param name="bp"></param>
        public Point LocalToGlobal(Point point, Point bp = null)
        {
            throw new Exception("未实现DisplayObject.LocalToGlobal");

            //let s = this;
            //if (s.parent) {
            //    //下一级的坐标始终应该是相对父级来说的，所以是用父级的矩阵去转换
            //    return s.parent.cMatrix.transformPoint(point.x, point.y, bp);
            //} else {
            //    //没有父级
            //    return s.cMatrix.transformPoint(point.x, point.y, bp);
            //}
        }


        #region 静态点缓存
        //为了hitTestPoint，localToGlobal，globalToLocal等方法不复新不重复生成新的点对象而节约内存
        public static Point Bp { get; set; } = new Point();
        public static Point P1 { get; set; } = new Point();
        public static Point P2 { get; set; } = new Point();
        public static Point P3 { get; set; } = new Point();
        public static Point P4 { get; set; } = new Point();
        #endregion


        protected Rectangle _dragBounds = new Rectangle();
        protected bool _isDragCenter = false;
        protected Point _lastDragPoint = new Point();



        public int _isUseToMask = 0;


        /// <summary>
        /// 启动鼠标或者触摸拖动
        /// </summary>
        /// <param name="isCenter">指定将可拖动的对象锁定到指针位置中心 (true)，还是锁定到用户第一次单击该对象的位置 (false) 默认false</param>
        /// <param name="bounds">相对于显示对象父级的坐标的值，用于指定 Sprite 约束矩形</param>
        public void StartDrag(bool isCenter = false, Rectangle bounds = null)
        {
            if (Stage == null)
            {
                //console.log("The DisplayObject is not on stage");
                return;
            }
            Stage.DragDisplay = this;

            _isDragCenter = isCenter;
            _lastDragPoint.X = float.MaxValue;
            _lastDragPoint.Y = float.MaxValue;

            if (bounds != null)
            {
                _dragBounds.X = bounds.X;
                _dragBounds.Y = bounds.Y;
                _dragBounds.Width = bounds.Width;
                _dragBounds.Height = bounds.Height;
            }
            else
            {
                _dragBounds.X = 0;
                _dragBounds.Y = 0;
                _dragBounds.Width = 0;
                _dragBounds.Height = 0;
            }
        }

        /// <summary>
        /// 停止鼠标或者触摸拖动
        /// </summary>
        public void StopDrag()
        {
            if (Stage.DragDisplay == this)
            {
                Stage.DragDisplay = null;
            }
        }


        /// <summary>
        /// 获取对象的自身的没有任何形变的原始姿态下的原点坐标及宽高,抽象方法
        /// </summary>
        /// <returns></returns>
        public virtual Rectangle GetBounds() {
            return _bounds;
        }

        /// <summary>
        /// 点击碰撞测试,就是舞台上的一个point是否在显示对象内,在则返回该对象，不在则返回null
        /// </summary>
        /// <param name="hitPoint">要检测碰撞的点</param>
        /// <param name="isGlobalPoint">是不是全局坐标的点,默认false是本地坐标</param>
        /// <param name="isMustMouseEnable">是不是一定要MouseEnable为true的显示对象才接受点击测试,默认为不需要 false</param>
        /// <returns></returns>
        public DisplayObject HitTestPoint(Point hitPoint,bool isGlobalPoint = false, bool isMustMouseEnable = false)
        {
            if(Visible || (!MouseEnable&& isMustMouseEnable))
            {
                return null;
            }

            Point point;

            if (isGlobalPoint)
            {
                point = GlobalToLocal(hitPoint, DisplayObject.Bp);
            }
            else
            {
                point = hitPoint;
            }
            if (GetBounds().IsPointIn(point))
            {
                return this;
            }
            return null;
        }




        /// <summary>
        /// 获取对象形变后外切矩形
        /// 可以从这个方法中读取到此显示对象变形后x方向上的宽和y方向上的高
        /// </summary>
        /// <returns></returns>
        public Rectangle GetDrawRect()
        {
            var rect = GetBounds();
            if (Mask != null)
            {
                var maskRect = Mask.GetDrawRect();
                if (rect.X < maskRect.X)
                {
                    rect.X = maskRect.X;
                }
                if (rect.Y < maskRect.Y)
                {
                    rect.Y = maskRect.Y;
                }
                if (rect.Width > maskRect.Width)
                {
                    rect.Width = maskRect.Width;
                }
                if (rect.Height > maskRect.Height)
                {
                    rect.Height = maskRect.Height;
                }
            }

            Matrix.TransformPoint(rect.X, rect.Y, P1);
            Matrix.TransformPoint(rect.X + rect.Width, rect.Y, P2);
            Matrix.TransformPoint(rect.X + rect.Width, rect.Y + rect.Height, P3);
            Matrix.TransformPoint(rect.X, rect.Y + rect.Height, P4);
            Rectangle.CreateFromPoints(_drawRect, P1, P3, P4);

            return _drawRect;
        }

        /// <summary>
        /// 缓存起来的纹理对象。最后真正送到渲染器去渲染的对象
        /// 与AnnieJs差异，使用SKImage类型对象作为纹理
        /// </summary>
        public SKImage Texture { get; set; } = null;

        protected Rectangle _bounds = new Rectangle();

        protected Rectangle _drawRect = new Rectangle();

        public float OffsetX { get; set; } = 0;
        public float OffsetY { get; set; } = 0;

        /// <summary>
        /// 更新函数
        /// </summary>
        protected void UpdateMatrix()
        {
            var UI = _UI;
            if (ParentChanged)
            {
                UI.UM = UI.UA = UI.UF = true;
                ParentChanged = false;
            }
            else
            {
                if (Parent!=null)
                {
                    var PUI = Parent._UI;
                    if (PUI.UM)
                    {
                        UI.UM = true;
                    }
                    if (PUI.UA)
                    {
                        UI.UA = true;
                    }
                    if (PUI.UF)
                    {
                        UI.UF = true;
                    }
                }
            }
            if (UI.UM)
            {
                //s._matrix.createBox(s._x, s._y, s._scaleX, s._scaleY, s._rotation, s._skewX, s._skewY, s._anchorX, s._anchorY);
                //s.cMatrix.setFrom(s._matrix);
                //if (s.parent)
                //{
                //    s.cMatrix.prepend(s.parent.cMatrix);
                //}
            }
            if (UI.UA)
            {
                //s.cAlpha = s._alpha;
                //if (s.parent)
                //{
                //    s.cAlpha *= s.parent.cAlpha;
                //}
            }
            if (UI.UF)
            {
                //s.cFilters.length = 0;
                //let sf = s._filters;
                //if (sf)
                //{
                //    let len = sf.length;
                //    for (let i = 0; i < len; i++)
                //    {
                //        s.cFilters.push(sf[i]);
                //    }
                //}
                //if (s.parent)
                //{
                //    if (s.parent.cFilters.length > 0)
                //    {
                //        let len = s.parent.cFilters.length;
                //        let pf = s.parent.cFilters;
                //        for (let i = len - 1; i >= 0; i--)
                //        {
                //            s.cFilters.unshift(pf[i]);
                //        }
                //    }
                //}
            }
            throw new Exception("未实现的方法UpdateMatrix");

        }

        /// <summary>
        /// 调用此方法将显示对象渲染到屏幕
        /// </summary>
        public virtual void Render(IRender render)
        {
            if(Visible)
            {
                UpdateMatrix();
                if(CAlpha > 0)
                {
                    var cf = cFilters;
                    var cfLen = cFilters.Count;
                    var fId = -1;
                    if (cfLen>0)
                    {
                        for (var i = 0; i < cfLen; i++)
                        {
                            throw (new Exception("未实现"));
                            //if (cFilters[i].type == "Shadow")
                            //{
                            //    fId = i;
                            //    break;
                            //}
                        }
                        if (fId >= 0)
                        {
                            throw (new Exception("未实现"));
                            //let ctx: any = renderObj["_ctx"];
                            //ctx.shadowBlur = cf[fId].blur;
                            //ctx.shadowColor = cf[fId].color;
                            //ctx.shadowOffsetX = cf[fId].offsetX;
                            //ctx.shadowOffsetY = cf[fId].offsetY;
                            //renderObj.draw(s);
                            //ctx.shadowBlur = 0;
                            //ctx.shadowOffsetX = 0;
                            //ctx.shadowOffsetY = 0;
                        }
                        else
                        {
                            render.Draw(this);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 更新流程走完之后再执行脚本和事件执行流程
        /// </summary>
        /// <param name="callState"></param>

        public virtual void UpdateEventAndScript(UpdateState callState)
        {
            throw new Exception("要先实现事件");
        }



        #region 差异化
        protected Rectangle _rect = null;

        /// <summary>
        /// 有时候一张贴图，我们只需要显示他的部分。其他不显示,对你可能猜到了
        /// SpriteSheet就用到了这个属性。默认为null表示全尺寸显示bitmapData需要显示的范围
        /// !与AnnieJs差异：该属性由Bitmap提取到DisplayObject,一般在其他类型对象中时恒为null
        /// </summary>
        /// <returns></returns>
        public Rectangle Rect
        {
            get{
                return _rect;
            }
            set
            {
                _rect = value;
                _UI.UD = true;
            }
        }


        #endregion

        public override void Destroy()
        {
            //非托管资源必须手动释放
            if(Texture!=null)
            {
                Texture.Dispose();
            }
            base.Destroy();
        }


    }


    public class UpdateObjectConfig
    {
        public bool UD { get; set; } 

        /// <summary>
        /// 是否更新矩阵
        /// </summary>
        public bool UM { get; set; }

        /// <summary>
        /// 是否更新Alpha
        /// </summary>
        public bool UA { get; set; }

        /// <summary>
        /// 是否更新滤镜
        /// </summary>
        public bool UF { get; set; }
    }


}
